﻿using System;
using System.ComponentModel;
using System.Linq.Expressions;
using MyFramework.Command.Interfaces;

namespace MyFramework.Command.DelegateWatchCommand
{
    /// <summary>
    /// Property Watch
    /// </summary>
    public class PropertyWatch : IPropertyWatch
    {
        #region Fields

        /// <summary>
        /// The prop name
        /// </summary>
        private string propName;

        /// <summary>
        /// The getter
        /// </summary>
        private Func<INotifyPropertyChanged, INotifyPropertyChanged> getter;

        /// <summary>
        /// The source
        /// </summary>
        private INotifyPropertyChanged source;

        #endregion

        #region constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyWatch"/> class.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="getter">The getter.</param>
        public PropertyWatch(string propertyName, Func<INotifyPropertyChanged, INotifyPropertyChanged> getter)
        {
            if (string.IsNullOrWhiteSpace(propertyName))
            {
                throw new ArgumentOutOfRangeException("propertyName must be not empty");
            }
            if (getter == null)
            {
                throw new ArgumentNullException("getter");
            }
            propName = propertyName;
            this.getter = getter;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyWatch"/> class.
        /// </summary>
        /// <param name="getterExpr">The getter expr.</param>
        public PropertyWatch(Expression<Func<INotifyPropertyChanged, INotifyPropertyChanged>> getterExpr)
        {
            if (getterExpr == null)
            {
                throw new ArgumentNullException("getterExpr");
            }
            propName = PropertySelectorUtils.GetPropertyName(getterExpr);
            getter = getterExpr.Compile();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the next.
        /// </summary>
        /// <value>
        /// The next.
        /// </value>
        public IPropertyWatch Next { get; set; }

        /// <summary>
        /// Gets the name of the property.
        /// </summary>
        /// <value>
        /// The name of the property.
        /// </value>
        public string PropertyName
        {
            get { return propName; }
        }

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        /// <value>
        /// The source.
        /// </value>
        public INotifyPropertyChanged Source
        {
            get { return source; }
            set
            {
                if (source != value)
                {
                    if (source != null)
                    {
                        source.PropertyChanged -= new PropertyChangedEventHandler(source_PropertyChanged);
                        source = value;
                        source.PropertyChanged += new PropertyChangedEventHandler(source_PropertyChanged);
                    }
                    source = value;
                    this.OnWatchedPropertyChanged(propName);
                }
            }
        }

        #endregion

        /// <summary>
        /// Handles the PropertyChanged event of the _source control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        private void source_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            this.OnWatchedPropertyChanged(e.PropertyName);
        }

        /// <summary>
        /// Doesnt copy Next, doesnt copy Source.
        /// </summary>
        /// <returns></returns>
        public PropertyWatch Clone()
        {
            return new PropertyWatch(this.PropertyName, this.getter);
        }

        /// <summary>
        /// Gets the tail.
        /// </summary>
        /// <returns></returns>
        public IPropertyWatchTail GetTail()
        {
            return this.Next == null ? null : this.Next.GetTail();
        }

        /// <summary>
        /// Called when [watched property changed].
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        private void OnWatchedPropertyChanged(string propertyName)
        {
            if (this.Next != null)
            {
                if (propertyName == propName)
                {
                    this.Next.Source = getter(this.Source);
                }
            }
        }
    }
}
